package org.trinity.netdisk.util;

import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.util.DigestUtils;
import org.springframework.util.ResourceUtils;
import org.trinity.netdisk.exception.file.CreateFileFailureException;

import javax.servlet.ServletOutputStream;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipInputStream;

public class FileUtil {
    public static final String sep = File.separator;
    //一串md5字符串占用字节
    private static final int md5Bytes = 32;

    public static boolean unzipInputStreamToDir(File file, File target) {
        if (getTargetDir() == null) {
            return false;
        }
        try {
            ZipInputStream zipInputStream = new ZipInputStream(new FileInputStream(file));
            ZipFile zipFile = new ZipFile(file);
            ZipEntry entry;
            File zipContent;
            String filename;
            while ((entry = zipInputStream.getNextEntry()) != null) {
                filename = DigestUtils.md5DigestAsHex(zipFile.getInputStream(entry));
                zipContent = new File(target.getAbsolutePath() + sep + filename);
                IOUtils.copy(zipFile.getInputStream(entry), new FileOutputStream(zipContent));
            }
            zipFile.close();
            zipInputStream.close();
            return true;
        } catch (IOException e) {
            e.printStackTrace();
            return false;
        }
    }


    public static File getFile(String filename) {
        File target = null;
        try {
            target = new File(ResourceUtils.getURL("file:").getPath());
            target = new File(target.getAbsolutePath() + sep + filename);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        return target;
    }

    public static File getTargetDir() {
        File file = getFile("static");
        return file.exists() || file.mkdir() ? file : null;
    }

    public static File getResource(String md5) {
        File targetDir = getTargetDir();
        File target = null;
        if (targetDir != null) {
            target = new File(targetDir.getAbsolutePath() + sep + md5);
        }
        return target == null || !target.exists() ? null : target;
    }

    public static File getTempDir() {
        File file = getFile("temp");
        return file.exists() || file.mkdir() ? file : null;
    }

    public static void createDirIfAbsent(Path dirPath) {
        File file = dirPath.toFile();
        if (!file.exists()) {
            file.mkdirs();
        }
    }

    public static void uploadFile(FileInputStream fileInputStream, Path targetFilePath) throws IOException {
        FileChannel inch = fileInputStream.getChannel();
        FileChannel outch = FileChannel.open(targetFilePath, StandardOpenOption.READ, StandardOpenOption.WRITE, StandardOpenOption.CREATE);
        MappedByteBuffer inmap = inch.map(FileChannel.MapMode.READ_ONLY, 0, inch.size());
        MappedByteBuffer outmap = outch.map(FileChannel.MapMode.READ_WRITE, 0, inch.size());
        byte[] bytes = new byte[inmap.limit()];
        inmap.get(bytes);
        outmap.put(bytes);
        inch.close();
        outch.close();
    }

    public static void saveChunkInfo(String baseFilePath, int index, String md5, String chunkMD5) throws IOException {

        Path filePath = Paths.get(baseFilePath, md5);
        FileUtil.createDirIfAbsent(filePath);

        java.io.File chunkInfo = new java.io.File(filePath.toFile(), "chunkInfo");
        //创建文件失败
        if (!chunkInfo.exists() && !chunkInfo.createNewFile()) {
            throw new CreateFileFailureException();
        }
        RandomAccessFile randomAccessFile = new RandomAccessFile(chunkInfo, "rwd");
        randomAccessFile.seek(md5Bytes * (index - 1L));
        randomAccessFile.write(chunkMD5.getBytes(StandardCharsets.UTF_8));
    }


    public static Path getChunkPath(String baseFilePath, int index, String md5) throws IOException {
        byte[] chunkFilenameBytes = new byte[32];
        Path filePath = Paths.get(baseFilePath, md5);
        java.io.File chunkInfo = new java.io.File(filePath.toFile(), "chunkInfo");
        if (!filePath.toFile().exists() || !chunkInfo.exists()) {
            throw new FileNotFoundException();
        }
        RandomAccessFile randomAccessFile = new RandomAccessFile(chunkInfo, "r");
        randomAccessFile.seek(md5Bytes * (index - 1L));
        randomAccessFile.read(chunkFilenameBytes);
        return Paths.get(filePath.toString(), new String(chunkFilenameBytes, StandardCharsets.UTF_8));
    }

    public static boolean isValidChunk(Path chunkPath) throws IOException {
        //文件md5是否跟传入md5一致
        return DigestUtils.md5DigestAsHex(new FileInputStream(chunkPath.toFile()))
                .equals(chunkPath.toFile().getName());
    }

    /**
     * @param md5          文件md5
     * @param baseFilePath storage文件夹
     * @param chunkNum     分块总数
     * @return 返回第一块损坏分块块index，没有损坏块则返回-1
     */
    public static int chunkChecker(String md5, String baseFilePath, int chunkNum) throws IOException {
        Path chunkPath;
        for (int i = 1; i <= chunkNum; i++) {
            chunkPath = getChunkPath(baseFilePath, i, md5);
            if (!isValidChunk(chunkPath)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * @param fileMd5 合并指定md5的文件分块
     * @throws IOException
     */
    public static void mergeFiles(String baseFilePath, String fileMd5, Integer allChunkNum) throws IOException {
        Path mergeTargetPath = Paths.get(baseFilePath, fileMd5 + "_");
        Path containerPath = Paths.get(baseFilePath, fileMd5);
        Path chunkPathInfo = Paths.get(containerPath.toString(), "chunkInfo");
        for (int i = 1; i <= allChunkNum; ++i) {
            Path chunkPath = getChunkPath(baseFilePath, i, fileMd5);
            Files.write(mergeTargetPath, Files.readAllBytes(chunkPath), StandardOpenOption.APPEND, StandardOpenOption.CREATE, StandardOpenOption.SYNC, StandardOpenOption.WRITE);
            chunkPath.toFile().delete();
        }
        chunkPathInfo.toFile().delete();
        File containerFile = containerPath.toFile();
        containerFile.delete();
        //更改名称为 baseFilePath + fileMd5
        mergeTargetPath.toFile().renameTo(containerFile);
    }

    /**
     * nio文件下载工具类
     *
     * @param targetFilePath 目标文件路径
     * @param outputStream   文件输出流
     * @param bufferSize     buffer 大小
     * @param off            下载开始位置
     */
    public static void downloadFile(Path targetFilePath, ServletOutputStream outputStream, int bufferSize, Long off) {
        try (FileChannel fileChannel = FileChannel.open(targetFilePath, StandardOpenOption.READ)) {
            ByteBuffer byteBuffer = ByteBuffer.allocateDirect(bufferSize);
            RateLimitBuffer rateLimitBuffer = new RateLimitBuffer(bufferSize);
            fileChannel.position(off);
            int getNum;
            while (fileChannel.read(byteBuffer) > 0) {
                byteBuffer.flip();
                while (byteBuffer.hasRemaining()) {
                    getNum = Math.min(byteBuffer.remaining(), bufferSize);
                    byteBuffer.get(rateLimitBuffer.getBuff(), 0, getNum);
                    rateLimitBuffer.read(outputStream);
                }
                byteBuffer.clear();
            }
        }catch (IOException e) {
            e.printStackTrace();
        }
    }
}
